/*
@auther fenglei
*/
package xxutil

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"strings"
)

// FileType 表示文件的压缩类型
type FileType string

const (
	TypeUnknown  FileType = "unknown" // 未知类型
	TypeGzip     FileType = "gzip"    // Gzip 格式
	TypeTarGz    FileType = "tar.gz"  // Tar+Gzip 格式
	TypeZip      FileType = "zip"     // ZIP 格式
	TypeTar      FileType = "tar"     // Tar 格式
	TypeRar      FileType = "rar"     // RAR 格式
	Type7z       FileType = "7z"      // 7z 格式
	TypeBzip2    FileType = "bzip2"   // BZip2 格式
	TypeTarBzip2 FileType = "tar.bz2" // Tar+BZip2 格式
	TypeXz       FileType = "xz"      // XZ 格式
	TypeTarXz    FileType = "tar.xz"  // Tar+XZ 格式
)

// GetFileCompressType 获取文件的压缩类型
// 通过检查文件头部的魔数（特征字节）来确定文件类型
func GetFileCompressType(filePath string) (FileType, error) {
	// 打开文件
	file, err := os.Open(filePath)
	if err != nil {
		return TypeUnknown, fmt.Errorf("打开文件失败: %w", err)
	}
	defer file.Close()

	// 读取文件头部，大多数压缩格式的特征都在前16个字节内
	header := make([]byte, 16)
	_, err = file.Read(header)
	if err != nil {
		return TypeUnknown, fmt.Errorf("读取文件头部失败: %w", err)
	}

	// 检查 ZIP 格式 (50 4B 03 04)
	if len(header) >= 4 && header[0] == 0x50 && header[1] == 0x4B && header[2] == 0x03 && header[3] == 0x04 {
		return TypeZip, nil
	}

	// 检查 GZIP 格式 (1F 8B)
	if len(header) >= 2 && header[0] == 0x1F && header[1] == 0x8B {
		// 重置文件指针
		if _, err = file.Seek(0, 0); err != nil {
			return TypeGzip, nil // 无法进一步检查，但确定是 Gzip
		}

		// 尝试创建 gzip 读取器
		gzReader, err := gzip.NewReader(file)
		if err != nil {
			return TypeGzip, nil // Gzip 格式，但可能不是有效的 Gzip 文件
		}
		defer gzReader.Close()

		// 检查是否为 TAR.GZ (通过尝试读取 tar 头部)
		tarReader := tar.NewReader(gzReader)
		_, err = tarReader.Next()
		if err == nil || err != io.EOF {
			return TypeTarGz, nil
		}

		return TypeGzip, nil
	}

	// 检查 RAR 格式 (52 61 72 21)
	if len(header) >= 4 && header[0] == 0x52 && header[1] == 0x61 && header[2] == 0x72 && header[3] == 0x21 {
		return TypeRar, nil
	}

	// 检查 7z 格式 (37 7A BC AF 27 1C)
	if len(header) >= 6 && header[0] == 0x37 && header[1] == 0x7A && header[2] == 0xBC &&
		header[3] == 0xAF && header[4] == 0x27 && header[5] == 0x1C {
		return Type7z, nil
	}

	// 检查 BZip2 格式 (42 5A)
	if len(header) >= 3 && header[0] == 0x42 && header[1] == 0x5A && header[2] == 0x68 {
		// 尝试检查是否为 tar.bz2 (需要更复杂的解析)
		if isTarBzip2(filePath) {
			return TypeTarBzip2, nil
		}
		return TypeBzip2, nil
	}

	// 检查 XZ 格式 (FD 37 7A 58 5A 00)
	if len(header) >= 6 && header[0] == 0xFD && header[1] == 0x37 && header[2] == 0x7A &&
		header[3] == 0x58 && header[4] == 0x5A && header[5] == 0x00 {
		// 尝试检查是否为 tar.xz (需要更复杂的解析)
		if isTarXz(filePath) {
			return TypeTarXz, nil
		}
		return TypeXz, nil
	}

	// 检查 TAR 格式 (这需要检查特定偏移量的字节或文件后缀)
	// TAR 文件没有统一的魔数，需要检查文件结构
	if isTarFile(filePath) {
		return TypeTar, nil
	}

	// 未识别的格式
	return TypeUnknown, nil
}

// isTarFile 检查文件是否为 TAR 格式
func isTarFile(filePath string) bool {
	file, err := os.Open(filePath)
	if err != nil {
		return false
	}
	defer file.Close()

	tarReader := tar.NewReader(file)
	_, err = tarReader.Next()
	return err == nil
}

// isTarBzip2 检查文件是否为 TAR.BZ2 格式
func isTarBzip2(filePath string) bool {
	// 实现更复杂，需要先解压 BZip2，然后检查 TAR 格式
	// 这里简化处理，只检查文件扩展名
	return strings.HasSuffix(strings.ToLower(filePath), ".tar.bz2") ||
		strings.HasSuffix(strings.ToLower(filePath), ".tbz2")
}

// isTarXz 检查文件是否为 TAR.XZ 格式
func isTarXz(filePath string) bool {
	// 实现更复杂，需要先解压 XZ，然后检查 TAR 格式
	// 这里简化处理，只检查文件扩展名
	return strings.HasSuffix(strings.ToLower(filePath), ".tar.xz") ||
		strings.HasSuffix(strings.ToLower(filePath), ".txz")
}

// GetFileExtensionByType 根据文件类型返回对应的扩展名
func GetFileExtensionByType(fileType FileType) string {
	switch fileType {
	case TypeGzip:
		return ".gz"
	case TypeTarGz:
		return ".tar.gz"
	case TypeZip:
		return ".zip"
	case TypeTar:
		return ".tar"
	case TypeRar:
		return ".rar"
	case Type7z:
		return ".7z"
	case TypeBzip2:
		return ".bz2"
	case TypeTarBzip2:
		return ".tar.bz2"
	case TypeXz:
		return ".xz"
	case TypeTarXz:
		return ".tar.xz"
	default:
		return ""
	}
}

// IsCompressedFile 检查文件是否为压缩文件
func IsCompressedFile(filePath string) bool {
	fileType, _ := GetFileCompressType(filePath)
	return fileType != TypeUnknown
}
